/* Simple HTTP Server Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include "esp_netif.h"

#include "esp_tls_crypto.h"
#include <esp_http_server.h>
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_tls.h"
#include "esp_check.h"

#include <esp_wifi.h>
#include <esp_system.h>
#include "nvs_flash.h"

#include "motor_server.h"
#include "tmc2209.h"
#include "vbus_adc.h"
#include "led.h"

#define EXAMPLE_HTTP_QUERY_KEY_MAX_LEN  (64)

/* A simple example that demonstrates how to create GET and POST
 * handlers for the web server.
 */

static const char *TAG = "motor_server";

QueueHandle_t motor_server_queue;

/* An HTTP GET handler */
static esp_err_t voltage_get_handler(httpd_req_t *req)
{
    char buf_str[10] = {"12"};

    /* Send response with custom headers and body set as the
     * string passed in user context*/

    memset(buf_str, 0, sizeof(buf_str));
    sprintf(buf_str, "%.2f V", vbus_adc_get());
    const char* resp_str = (const char*) buf_str;
    httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);

    led_queuesend_state(LED_FLASH);
    return ESP_OK;
}

static const httpd_uri_t voltage = {
    .uri       = "/voltage",
    .method    = HTTP_GET,
    .handler   = voltage_get_handler,
    /* Let's pass response string in user
     * context to demonstrate it's usage */
    .user_ctx  = "12"
};

static esp_err_t motor_status_get_handler(httpd_req_t *req)
{
    char buf_str[2][10] = {"Enable", "Disable"};

    /* Send response with custom headers and body set as the
     * string passed in user context*/
    static uint8_t i = 0;

    const char* resp_str = buf_str[i++ % 2];
    httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);

    led_queuesend_state(LED_FLASH);
    return ESP_OK;
}

static const httpd_uri_t motor_status = {
    .uri       = "/status",
    .method    = HTTP_GET,
    .handler   = motor_status_get_handler,
    /* Let's pass response string in user
     * context to demonstrate it's usage */
    .user_ctx  = "EN"
};

static esp_err_t slider_get_handler(httpd_req_t *req)
{
    char buf_str[5] = {"12"};

    /* Send response with custom headers and body set as the
     * string passed in user context*/

    memset(buf_str, 0, sizeof(buf_str));
    sprintf(buf_str, "%d", get_cur_pos_per());

    const char* resp_str = (const char*) buf_str;
    httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);

    ESP_LOGI(TAG, "%s send data: %s", __func__, resp_str);

    led_queuesend_state(LED_FLASH);

    return ESP_OK;
}

static const httpd_uri_t slider = {
    .uri       = "/slider",
    .method    = HTTP_GET,
    .handler   = slider_get_handler,
};

static esp_err_t speed_accel_get_handler(httpd_req_t *req)
{
    char buf_str[5] = {"12"};

    const char *item = req->user_ctx;

    /* Send response with custom headers and body set as the
     * string passed in user context*/

    memset(buf_str, 0, sizeof(buf_str));

    if (strstr(item, "speed"))
        sprintf(buf_str, "%ld", get_speed());
    else if (strstr(item, "accel"))
        sprintf(buf_str, "%ld", get_accel());

    const char* resp_str = (const char*) buf_str;
    httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);

    ESP_LOGI(TAG, "%s send data: %s", __func__, resp_str);

    led_queuesend_state(LED_FLASH);

    return ESP_OK;
}

static const httpd_uri_t speed = {
    .uri       = "/speed",
    .method    = HTTP_GET,
    .handler   = speed_accel_get_handler,
    .user_ctx  = "speed"
};

static const httpd_uri_t accel = {
    .uri       = "/accel",
    .method    = HTTP_GET,
    .handler   = speed_accel_get_handler,
    .user_ctx  = "accel"
};

/* An HTTP POST handler */
static esp_err_t update_post_handler(httpd_req_t *req)
{
    char buf[100];
    int ret, remaining = req->content_len;

    led_queuesend_state(LED_FLASH);

    while (remaining > 0) {
        /* Read the data for the request */
        if ((ret = httpd_req_recv(req, buf,
                        MIN(remaining, sizeof(buf)))) <= 0) {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
                /* Retry receiving if timeout occurred */
                continue;
            }
            return ESP_FAIL;
        }

        /* Send back the same data */
        httpd_resp_send_chunk(req, buf, ret);
        remaining -= ret;

        /* Log data received */
        ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
        ESP_LOGI(TAG, "%.*s", ret, buf);
        ESP_LOGI(TAG, "====================================");
    }

    buf[req->content_len] = '\0';

    tmc_queue_t tmcq;

    memset(&tmcq, 0, sizeof(tmc_queue_t));
    memcpy(tmcq.data.buf, buf, req->content_len);
    tmcq.data.buf[req->content_len] = '\0';
    tmcq.type = TMC_CMD;

    xQueueSend(tmc_queue, &tmcq, pdMS_TO_TICKS(200));
    //tmc_queue_prase(&tmcq);

    // End response
    httpd_resp_send_chunk(req, NULL, 0);

    return ESP_OK;
}

static const httpd_uri_t update = {
    .uri       = "/update",
    .method    = HTTP_POST,
    .handler   = update_post_handler,
    .user_ctx  = NULL
};

/* This handler allows the custom error handling functionality to be
 * tested from client side. For that, when a PUT request 0 is sent to
 * URI /ctrl, the /hello and /echo URIs are unregistered and following
 * custom error handler http_404_error_handler() is registered.
 * Afterwards, when /hello or /echo is requested, this custom error
 * handler is invoked which, after sending an error message to client,
 * either closes the underlying socket (when requested URI is /echo)
 * or keeps it open (when requested URI is /hello). This allows the
 * client to infer if the custom error handler is functioning as expected
 * by observing the socket state.
 */
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
    if (strcmp("/hello", req->uri) == 0) {
        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/hello URI is not available");
        /* Return ESP_OK to keep underlying socket open */
        return ESP_OK;
    } else if (strcmp("/echo", req->uri) == 0) {
        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/echo URI is not available");
        /* Return ESP_FAIL to close underlying socket */
        return ESP_FAIL;
    }
    /* For any other URI send 404 and close socket */
    httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Some 404 error message");
    return ESP_FAIL;
}


/* An HTTP POST handler */
static esp_err_t save_post_handler(httpd_req_t *req)
{
    char buf[100];
    int ret, remaining = req->content_len;

    led_queuesend_state(LED_FLASH);

    while (remaining > 0) {
        /* Read the data for the request */
        if ((ret = httpd_req_recv(req, buf,
                        MIN(remaining, sizeof(buf)))) <= 0) {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
                /* Retry receiving if timeout occurred */
                continue;
            }
            return ESP_FAIL;
        }

        /* Send back the same data */
        httpd_resp_send_chunk(req, buf, ret);
        remaining -= ret;

        /* Log data received */
        ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
        ESP_LOGI(TAG, "%.*s", ret, buf);
        ESP_LOGI(TAG, "====================================");
    }

    tmc_queue_t tmcq;
    memset(&tmcq, 0, sizeof(tmc_queue_t));
    memcpy(tmcq.data.buf, buf, req->content_len);
    tmcq.data.buf[req->content_len] = '\0';
    tmcq.type = TMC_CMD;

    xQueueSend(tmc_queue, &tmcq, pdMS_TO_TICKS(200));

    // End response
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

static const httpd_uri_t save = {
    .uri       = "/save",
    .method    = HTTP_POST,
    .handler   = save_post_handler,
    .user_ctx  = NULL
};

extern const unsigned char index_script_start[] asm("_binary_index_html_html_start");
extern const unsigned char index_script_end[]   asm("_binary_index_html_html_end");

static esp_err_t root_get_handler(httpd_req_t *req)
{
    char*  buf;
    size_t buf_len;
    esp_err_t ret;
    uint8_t retry = 5;

    /* Get header value string length and allocate memory for length + 1,
     * extra byte for null termination */
    buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
    if (buf_len > 1) {
        buf = malloc(buf_len);
        ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
        /* Copy null terminated value string into buffer */
        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
            ESP_LOGI(TAG, "Found header => Host: %s", buf);
        }
        free(buf);
    }

    /* Send response with custom headers and body set as the
     * string passed in user context*/
    const size_t send_script_size = index_script_end - index_script_start;

retry_send:
    ret = httpd_resp_send_chunk(req, (const char *)index_script_start, send_script_size);
    if (ret != ESP_OK && --retry) {
        ESP_LOGE(TAG, "Failed to send script ret:%d retry:%d", ret, retry);
        goto retry_send;
    }

    httpd_resp_sendstr_chunk(req, NULL);

    led_queuesend_state(LED_FLASH);
    return ESP_OK;
}

static const httpd_uri_t root = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = root_get_handler,
    /* Let's pass response string in user
     * context to demonstrate it's usage */
    .user_ctx  = NULL
};

static httpd_handle_t start_webserver(void)
{
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    config.lru_purge_enable = true;

    // Start the httpd server
    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if (httpd_start(&server, &config) == ESP_OK) {
        // Set URI handlers
        ESP_LOGI(TAG, "Registering URI handlers");
        httpd_register_uri_handler(server, &root);
        httpd_register_uri_handler(server, &voltage);
        httpd_register_uri_handler(server, &motor_status);
        httpd_register_uri_handler(server, &slider);
        httpd_register_uri_handler(server, &speed);
        httpd_register_uri_handler(server, &accel);
        httpd_register_uri_handler(server, &save);
        httpd_register_uri_handler(server, &update);

        return server;
    }

    ESP_LOGI(TAG, "Error starting server!");
    return NULL;
}

static esp_err_t stop_webserver(httpd_handle_t server)
{
    // Stop the httpd server
    return httpd_stop(server);
}

static void stop_motor_server(httpd_handle_t* server)
{
    if (*server) {
        ESP_LOGI(TAG, "Stopping webserver");
        if (stop_webserver(*server) == ESP_OK) {
            *server = NULL;
        } else {
            ESP_LOGE(TAG, "Failed to stop http server");
        }
    }
}

static void start_motor_server(httpd_handle_t* server)
{
    if (*server == NULL) {
        ESP_LOGI(TAG, "Starting webserver");
        *server = start_webserver();
    }
}

void motor_server_send_cmd(uint8_t cmd)
{
    motor_server_queue_t msq;
    msq.cmd = cmd;
    xQueueSend(motor_server_queue, &msq, pdMS_TO_TICKS(200));
};

static void motor_server_task(void* arg)
{
    static httpd_handle_t server = NULL;

    motor_server_queue_t msq;

    while (1) {
        if (xQueueReceive(motor_server_queue, &msq, portMAX_DELAY) == pdTRUE) {
            if (msq.cmd == MOTOR_SERVER_CMD_START) {
                start_motor_server(&server);
            } else if (msq.cmd == MOTOR_SERVER_CMD_STOP) {
                stop_motor_server(&server);
            } else {
                ESP_LOGE(TAG, "Unknown command %d", msq.cmd);
            }
        } else {
            ESP_LOGE(TAG, "Failed to receive event");
        }
    }
}

void init_motor_server(void)
{
    motor_server_queue = xQueueCreate(10, sizeof(motor_server_queue_t));
    if (!motor_server_queue) {
        ESP_LOGE(TAG, "Creating tmc_queue failed");
        return;
    }

    xTaskCreate(motor_server_task, "motor_server_task", 8192, NULL, 8, NULL);

}
